const { join, isAbsolute, resolve } = require('path')
const { writeFileSync } = require('fs')
const parser = require('./parser')

class Compiler {
  constructor(options = {}) {
    this.options = options
    this.context = process.cwd()
    this.modules = []
  }

  run() {
    // 入口文件路径
    const entryPath = isAbsolute(this.options.entry) ? this.options.entry : join(this.context, this.options.entry)

    const fileInfo = this.build(entryPath)

    this.modules.push(fileInfo)

    const _that = this

    // 遍历所有模块 收集所有module
    for (let i = 0; i < this.modules.length; i++) {
      Object.values(_that.modules[i].deps).map(depPath => {
        const fileInfo = this.build(depPath)
        this.modules.push(fileInfo)
      })
    }

    // 生成依赖树
    const depsGraph = this.modules.reduce((prev, module) => {
      return {
        ...prev,
        [module.filepath]: {
          deps: module.deps,
          code: module.code
        }
      }
    }, {})

    this.generator(depsGraph)

  }

  // 编译某个文件模块
  build(filepath) {
    const { ast, source } = parser.getAst(filepath)
    const deps = parser.getDeps(filepath, ast)
    const code = parser.getCode(ast, source)
    return {
      filepath,
      deps,
      code
    }
  }

  // 生成bundle
  generator(depsGraph) {
    const entryPath = isAbsolute(this.options.entry) ? this.options.entry : join(this.context, this.options.entry)
    const bundle = `
      (function(depsGraph) {
        function require(module) {
          function localRequire(relativePath) {
            return require(depsGraph[module].deps[relativePath])
          }

          const exports = {};

          // exports 和 require 都是为了给code 中使用的，可以看看生成的code 是什么样的; 注意：使用匿名函数时不要忘记在前面加上 ';'
          (function (require, exports, code) {
            eval(code)
          })(localRequire, exports, depsGraph[module].code);

          return exports;
        }

        require(${JSON.stringify(entryPath)})
      })(${JSON.stringify(depsGraph)})
    `

    writeFileSync(resolve(this.options.output.path, this.options.output.filename), bundle)

  }

}

module.exports = Compiler;